package com.watson.todolist.database;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.watson.todolist.constants.DatabaseConstants;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

/**
 * The account manager class handles persisting the user's information by
 * directly interacting with the database. An AccountManager object allows
 * access to get a user's core data from the database and to put data inside of
 * the database.
 * 
 * @author Donnie Ward
 * 
 * @since 1.0
 */
public class AccountManager {
	/**
	 * Logger tag
	 */
	private static String TAG = "AccountManager Class";
	/**
	 * Database instance
	 */
	private SQLiteDatabase db;
	private static AccountManager accountManager;

	/**
	 * Account manager construction which initializes the database instance
	 * 
	 * @param db
	 *            - An instance of the database
	 */
	public AccountManager(SQLiteDatabase db) {
		this.db = db;
		accountManager = this;
	}

	/**
	 * Returns an instance of itself to prevent multiple instances being
	 * created, making this class a Singleton design pattern.
	 * 
	 * @return accountManager An instance of this class
	 */
	public static AccountManager getAccountManager() {
		return accountManager;
	}

	/**
	 * Creates a new user account. Post condition: User account with this user
	 * name should not exist, use the AccountManager userNameExistence() method
	 * to check.
	 * 
	 * @param userName
	 *            - User's user name
	 * @param emailAddress
	 *            - User's email address
	 * @param password
	 *            - User's password
	 * @return userId - The user id of the newly inserted row, or -1 if an error
	 *         occurred
	 * @throws GeneralSecurityException
	 */
	public long createUserAccount(String userName, String emailAddress,
			String password) throws GeneralSecurityException {
		
		if(userName == null || emailAddress == null || password == null){
			return -1;
		}
		// Encrypt password for storage
		String encryptPassword = GenericEncryption.encrypt(password);

		ContentValues values = createContentValues(userName, emailAddress,
				encryptPassword);
		return db.insert(DatabaseConstants.USER_ACCOUNT_TABLE_NAME, null,
				values);
	}

	/**
	 * Updates the user's account information. Depending on the type of update a
	 * new email address or password will be used.
	 * 
	 * @param userId
	 *            - User's account id to make updates on
	 * @param userName
	 *            - User's user name
	 * @param emailAddress
	 *            - User's email address
	 * @param password
	 *            - User's non-changed encrypted password or plain text new
	 *            password if change request code is a change password request
	 * @return successStatus - The success status of the account update, true if
	 *         successful
	 * @throws GeneralSecurityException
	 */
	public boolean updateUserAccount(long userId, String userName,
			String emailAddress, String password, int updateRequestCode)
			throws GeneralSecurityException {
		ContentValues values = createContentValues(userName, emailAddress,
				password);

		// Handle change password request
		if (updateRequestCode == DatabaseConstants.CHANGE_PASSWORD_REQUEST) {
			// Encrypt password
			password = GenericEncryption.encrypt(password);
			Log.d(TAG, "Updated user account password for user name: "
					+ userName);
			return db.update(DatabaseConstants.USER_ACCOUNT_TABLE_NAME, values,
					DatabaseConstants.ID + "=" + userId, null) > 0;
		}

		// Handle change email request
		if (updateRequestCode == DatabaseConstants.CHANGE_EMAIL_REQUEST) {
			Log.d(TAG, "Updated user account email for user name: " + userName);
			return db.update(DatabaseConstants.USER_ACCOUNT_TABLE_NAME, values,
					DatabaseConstants.ID + "=" + userId, null) > 0;
		}

		Log.d(TAG, "User account update failed");
		return false;
	}

	/**
	 * Deletes user account by user id
	 * 
	 * @param userId
	 *            - User's id
	 * @return
	 */
	public boolean deleteUserAccount(long userId) {
		return db.delete(DatabaseConstants.USER_ACCOUNT_TABLE_NAME,
				DatabaseConstants.ID + "=" + userId, null) > 0;
	}

	/**
	 * Authenticates user account primarily for login purposes
	 * 
	 * @param userName
	 *            - User's name
	 * @param password
	 *            - User's password
	 * @return validUser - true if valid user and false otherwise
	 * @throws GeneralSecurityException
	 */
	public boolean authenticateUserAccount(String userName, String password)
			throws GeneralSecurityException {
		// Gets row matching given user name
		if (userNameExistence(userName)) {
			Cursor cursor = db.query(true,
					DatabaseConstants.USER_ACCOUNT_TABLE_NAME, new String[] {
							DatabaseConstants.ID, DatabaseConstants.TIME_STAMP,
							DatabaseConstants.ACCOUNT_TABLE_USER_NAME,
							DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS,
							DatabaseConstants.ACCOUNT_TABLE_PASSWORD },
					DatabaseConstants.ACCOUNT_TABLE_USER_NAME + "=" + "'"
							+ userName + "'", null, null, null, null, null);

			// Check to see if any rows were brought back in the cursor
			if (cursor == null || cursor.getCount() == 0) {
				Log.d(TAG,
						"Invalid account authentication for account with user name: "
								+ userName);
				return false;
			} else {
				// Move cursor to first row
				cursor.moveToFirst();
			}

			// Encrypt password to check against database entry
			String encryptedPassword = GenericEncryption.encrypt(password);

			// Check to see if password matches
			if (encryptedPassword.equals(cursor.getString(cursor
					.getColumnIndex(DatabaseConstants.ACCOUNT_TABLE_PASSWORD)))) {
				Log.d(TAG,
						"Valid account authentication for account with user name: "
								+ userName);
				return true;
			}
		}

		Log.d(TAG,
				"Invalid account authentication for account with user name: "
						+ userName);
		return false;
	}

	/**
	 * Gets user account by user name
	 * 
	 * @param userId
	 *            - User's user name
	 * @return cursor - cursor which contains user information which could be
	 *         empty if no matches are found
	 * @throws SQLException
	 */
	public Cursor getUserAccountByUserName(String userName) throws SQLException {
		// Gets row matching given user name
		Cursor cursor = db.query(true,
				DatabaseConstants.USER_ACCOUNT_TABLE_NAME, new String[] {
						DatabaseConstants.ID, DatabaseConstants.TIME_STAMP,
						DatabaseConstants.ACCOUNT_TABLE_USER_NAME,
						DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS,
						DatabaseConstants.ACCOUNT_TABLE_PASSWORD },
				DatabaseConstants.ACCOUNT_TABLE_USER_NAME + "=" + "'"
						+ userName + "'", null, null, null, null, null);

		// Move cursor to first row
		if (cursor != null) {
			cursor.moveToFirst();
		}
		return cursor;
	}

	/**
	 * Gets user account by user email
	 * 
	 * @param userId
	 *            - User's email
	 * @return cursor - cursor which contains user information which could be
	 *         empty if no matches are found
	 * @throws SQLException
	 */
	public Cursor getUserAccountByUserEmail(String email) throws SQLException {
		// Gets row matching given user name
		Cursor cursor = db.query(true,
				DatabaseConstants.USER_ACCOUNT_TABLE_NAME, new String[] {
						DatabaseConstants.ID, DatabaseConstants.TIME_STAMP,
						DatabaseConstants.ACCOUNT_TABLE_USER_NAME,
						DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS,
						DatabaseConstants.ACCOUNT_TABLE_PASSWORD },
				DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS + "=" + "'"
						+ email + "'", null, null, null, null, null);

		// Move cursor to first row
		if (cursor != null) {
			cursor.moveToFirst();
		}
		return cursor;
	}

	/**
	 * Checks to see if the user exist
	 * 
	 * @param userName
	 *            - User's name
	 * @return existenceOfUser - true if user exist and false otherwise
	 */
	public boolean userNameExistence(String userName) {
		// Gets row matching given user name
		Cursor cursor = db.query(true,
				DatabaseConstants.USER_ACCOUNT_TABLE_NAME, new String[] {
						DatabaseConstants.ID, DatabaseConstants.TIME_STAMP,
						DatabaseConstants.ACCOUNT_TABLE_USER_NAME,
						DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS,
						DatabaseConstants.ACCOUNT_TABLE_PASSWORD },
				DatabaseConstants.ACCOUNT_TABLE_USER_NAME + "=" + "'"
						+ userName + "'", null, null, null, null, null);

		// Check to see if any rows were brought back in the cursor
		if (cursor == null || cursor.getCount() == 0) {
			Log.d(TAG, "Account does not exist for user name: " + userName);
			return false;
		} else {
			Log.d(TAG, "Account already exists for user name: " + userName);
			return true;
		}
	}

	/**
	 * Checks to see if the email exist
	 * 
	 * @param email
	 *            - User's email
	 * @return existenceOfEmail - true if email exist and false otherwise
	 */
	public boolean userEmailExistence(String email) {
		// Gets row matching given email
		Cursor cursor = db.query(true,
				DatabaseConstants.USER_ACCOUNT_TABLE_NAME, new String[] {
						DatabaseConstants.ID, DatabaseConstants.TIME_STAMP,
						DatabaseConstants.ACCOUNT_TABLE_USER_NAME,
						DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS,
						DatabaseConstants.ACCOUNT_TABLE_PASSWORD },
				DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS + "=" + "'"
						+ email + "'", null, null, null, null, null);

		// Check to see if any rows were brought back in the cursor
		if (cursor == null || cursor.getCount() == 0) {
			Log.d(TAG, "Account does not exist for user name with email: "
					+ email);
			return false;
		} else {
			Log.d(TAG, "Account exists for user name with email: " + email);
			return true;
		}
	}

	/**
	 * Send user an email containing the user account's password. This
	 * implementation is a generic way to send an email using Java Mail Api.
	 * 
	 * @param userEmailAddress
	 *            - User's email address
	 * @param userName
	 *            - User's name
	 * @param password
	 *            - User's non-changed encrypted password
	 * @return successStatus - true if sending email was successful and false
	 *         otherwise
	 * @throws IOException
	 * @throws GeneralSecurityException
	 * @throws MessagingException
	 * @throws AddressException
	 */
	public boolean sendUserPasswordCodeViaEmail(Context context,
			String userEmailAddress, String userName)
			throws GeneralSecurityException, IOException, AddressException,
			MessagingException {
		// Google account information
		String teamEmail = "teamwatson32@gmail.com";
		String teamUserName = "teamwatson32";
		String teamPassword = "teamwatson32";

		// Setting up properties for sending an email
		Properties props = new Properties();
		props.setProperty("mail.transport.protocol", "smtp");
		props.put("mail.host", "smtp.gmail.com");
		props.put("mail.smtp.auth", "true");
		props.put("mail.smtp.starttls.enable", "true");
		props.put("mail.smtp.auth", "true");
		props.put("mail.smtp.port", "465");
		props.put("mail.smtp.socketFactory.port", "465");
		props.put("mail.smtp.socketFactory.class",
				"javax.net.ssl.SSLSocketFactory");
		props.put("mail.smtp.socketFactory.fallback", "false");
		props.setProperty("mail.smtp.quitwait", "false");

		// Authenticates google account information and sets up message details
		Session session = Session.getInstance(props, new MailAuthenticator(
				teamUserName, teamPassword));
		Message message = new MimeMessage(session);
		message.setFrom(new InternetAddress(teamEmail));
		message.setRecipients(Message.RecipientType.TO,
				InternetAddress.parse(userEmailAddress));
		message.setSubject("Watson ToDo List Account Password Reset");
		message.setText("Hi " + userName + ". \n\nYour change password code: "
				+ GenericEncryption.encrypt(userName));

		// Sends email
		Transport transport = session.getTransport("smtp");
		transport.connect("smtp.gmail.com", 465, teamUserName, teamPassword);
		Transport.send(message);

		Log.d(TAG, "Password sent via email to user name " + userName);
		return true;
	}

	/**
	 * Creates content values
	 * 
	 * @param userName
	 *            - User's name
	 * @param emailAddress
	 *            - User's email address
	 * @param password
	 *            - User's password
	 * @return contentValues - content values
	 */
	private ContentValues createContentValues(String userName,
			String emailAddress, String password) {
		ContentValues values = new ContentValues();
		values.put(DatabaseConstants.ACCOUNT_TABLE_USER_NAME, userName);
		values.put(DatabaseConstants.ACCOUNT_TABLE_EMAIL_ADDRESS, emailAddress);
		values.put(DatabaseConstants.ACCOUNT_TABLE_PASSWORD, password);
		return values;
	}
}
